<template>
  <div class="hello">
    <div style="display:flex;">
      <div style="cursor:pointer;" @click="openFile"><slot></slot></div>
      <div style="flex:1;"></div>
    </div>
    <input style="display:none;" type="file" :ref="'input' + id" @change="openCanvas" />
    <el-dialog title="上传头像" :visible.sync="visible">
      <div :style="{ width:width + 'px',height:height + 'px' }" class="canvasBox">
        <canvas @mousedown="beginMove" @mousemove="endMove" @mouseup="endMove" @mouseout="endMove" @mousewheel="changeScale" :ref="'canvas' + id" :width="width" :height="height"></canvas>
      </div>
      <div class="btns">
        <el-button
          type="primary"
          size="mini"
          @click="submit"
          >{{uploading ? '上传中...' : '确定'}}</el-button
        >
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: 'uploadCanvas',
  props: {
    action: String,//上传路径
    onSuccess: Function,//成功回调
    fileKey:String,//图片关键字
    width:{//目标宽度
      type:Number,
      default:300
    },
    height:{//目标高度
      type:Number,
      default:300
    },
    cicle:Boolean//是否圆形裁剪
  },
  data(){
    return {
      id:new Date().getTime(),//使用时间戳作为每个上传控件的唯一标准
      visible:false,//弹框开关
      canvas:'',//dom元素
      ctx:'',//画布对象
      scale:0.5,//缩放
      x:0,//位移x
      y:0,//位移y
      image:'',//选中图片
      moveing:false,//移动状态
      downX:0,//mousedown后存储鼠标位置x
      downY:0,//mousedown后存储鼠标位置y
      startX:0,//mousedown后存储位移x
      startY:0,//mousedown后存储位移y
      uploading:false//上传状态
    }
  },
  methods:{
    upload(file){
      //创建表单数据
      let formData = new FormData()
      //最近上传内容
      formData.append(this.fileKey || 'file',file)
      //开启上传状态
      this.uploading = true
      //使用axios上传文件
      this.$http({
        method:'POST',
        url:this.action,
        data:formData,
        headers: { 'Content-Type': 'multipart/form-data' }//必须设置
      }).then(res => {
        //关闭上传状态
        this.uploading = false
        //关闭弹窗
        this.visible = false
        //回调成功
        this.onSuccess(res,file)
      })
    },
    openFile(){
      //调用浏览器选择文件
      this.$refs['input' + this.id].click();
    },
    //文件类型转DataURL
    fileTobase64(file,callback){
      //创建文件实例
      var f = new FileReader()
      //监听加载完成
      f.onload = function (e) {
        callback(e.target.result)
      }
      //读取文件
      f.readAsDataURL(file)
    },
    openCanvas(){
      //获取所选择的文件列表
      let files = this.$refs['input' + this.id].files
      
      //直接上传文件
      //this.upload(files[0])

      //编辑图片后上传
      //file类型转换为base64  
      this.fileTobase64(files[0],base64 => {
        //开启弹框
        this.visible = true
        //延迟执行
        this.$nextTick(()=>{
          //保存canvas元素
          this.canvas = this.$refs['canvas' + this.id]
          //保存画布操作对象
          this.ctx = this.canvas.getContext('2d')
          //保存所选图片
          this.image = new Image() //创建图片对象
          //监听图片加载完成
          this.image.onload = () =>{
            //开始渲染
            this.drawImage()
          }
          //设置图片路径
          this.image.src = base64
        })
      })
    },
    //修改缩放比例实现放大缩小
    changeScale(event){
      //滚动鼠标滑轮，通过wheelDelta值区分方向
      this.scale += event.wheelDelta > 0 ? 0.01 : -0.01
      //排除小于0的情况
      if(this.scale <= 0) this.scale = 0
      //渲染
      this.drawImage()
    },
    //mousedowm事件触发
    beginMove(event){
      //记录点击位置
      this.downX = event.pageX
      this.downY = event.pageY
      //记录图片位置
      this.startX = this.x
      this.startY = this.y
      //保存为移动状态
      this.moveing = true
    },
    endMove(event){
      //鼠标移动事件且在移动中
      if(this.moveing && event.type == 'mousemove'){
        //从新计算位置
        this.x = this.startX + (event.pageX - this.downX)
        this.y = this.startY + (event.pageY - this.downY)
        //渲染
        this.drawImage()
      }else{//其他事件类型退出移动状态
        if(this.moveing) this.moveing = false
      }
    },
    drawImage(){
      //通过设置画布元素的宽高重置画布
      this.canvas.width = this.width
      this.canvas.height = this.height
      //是否按圆形裁剪
      if(this.cicle){
        //绘制裁剪区域
        this.ctx.beginPath();
        //定义圆形区域
        this.ctx.arc(this.width / 2,this.height / 2,Math.min(this.width,this.height) / 2,0,2 * Math.PI);
        //裁剪
        this.ctx.clip()
      }
      //渲染图片
      this.ctx.drawImage(this.image,this.x,this.y,this.image.width * this.scale,this.image.height * this.scale)
      
    },
    //base64转换为blob
    base64ToBlob(base64){
      var arr = base64.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], {
        type: mime
      })
    },
    submit(){
      //canvas转为base64
      let base64 = this.canvas.toDataURL('image/png')
      //base64转换为blob
      let blob = this.base64ToBlob(base64)
      //上传blob
      this.upload(blob)
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .canvasBox{
    margin: 0 auto;
    background: #000;
  }
  .btns{
    text-align: right;
    margin-top:32px;
  }
</style>